#include "MKVdecode.hpp"
#include <sstream>
#include <stdexcept>
#include <cstring>

static int objectCounter=0;

void pad_append_handler(GstElement *src, GstPad *new_pad, MKV_Decode *data)
{
  GstPad *sink_pad = gst_element_get_static_pad (data->h264parse, "sink");
  GstPadLinkReturn ret;
  GstCaps *new_pad_caps = NULL;
  GstStructure *new_pad_struct = NULL;
  const gchar *new_pad_type = NULL;

  g_print ("Received new pad '%s' from '%s':\n", GST_PAD_NAME (new_pad), GST_ELEMENT_NAME (src));

  /* If our converter is already linked, we have nothing to do here */
  if (gst_pad_is_linked (sink_pad)) {
    g_print ("We are already linked. Ignoring.\n");
    goto exit;
  }

  /* Check the new pad's type */
  new_pad_caps = gst_pad_get_current_caps (new_pad);
  new_pad_struct = gst_caps_get_structure (new_pad_caps, 0);
  new_pad_type = gst_structure_get_name (new_pad_struct);
  if (!g_str_has_prefix (new_pad_type, "video/x-h264")) {
    g_print ("It has type '%s' which is not h264. Ignoring.\n", new_pad_type);
    goto exit;
  }

  /* Attempt the link */
  ret = gst_pad_link (new_pad, sink_pad);
  if (GST_PAD_LINK_FAILED (ret)) {
    g_print ("Type is '%s' but link failed.\n", new_pad_type);
  } else {
    g_print ("Link succeeded (type '%s').\n", new_pad_type);
  }

exit:
  /* Unreference the new pad's caps, if we got them */
  if (new_pad_caps != NULL)
    gst_caps_unref (new_pad_caps);

  /* Unreference the sink pad */
  gst_object_unref (sink_pad);
}

MKV_Decode::MKV_Decode(std::filesystem::path path)
{
  auto createOnlyName=[](const char* name){
    std::stringstream ss;
    ss<<name<<"_"<<objectCounter;
    objectCounter++;
    return ss.str();
  };
  filesrc=gst_element_factory_make("filesrc",createOnlyName("filesrc").c_str());
  matroskademux=gst_element_factory_make("matroskademux",createOnlyName("matroskademux").c_str());
  h264parse=gst_element_factory_make("h264parse",createOnlyName("h264parse").c_str());
  nvh264dec=gst_element_factory_make("nvh264dec",createOnlyName("nvh264dec").c_str());
  cudaconvert=gst_element_factory_make("cudaconvert",createOnlyName("cudaconvert").c_str());
  cudadownload=gst_element_factory_make("cudadownload",createOnlyName("cudadownload").c_str());
  appsink=gst_element_factory_make("appsink",createOnlyName("appsink").c_str());
  pipeline=gst_pipeline_new(createOnlyName("pipeline").c_str());

  if(!pipeline || !filesrc || !matroskademux || !h264parse || !nvh264dec || !cudaconvert || !cudadownload || !appsink)
    throw std::runtime_error("无法创建所有Gstreamer元素");
  gst_bin_add_many(GST_BIN(pipeline),filesrc,matroskademux,h264parse,nvh264dec,cudaconvert,cudadownload,appsink,nullptr);
  if(!gst_element_link_many(filesrc,matroskademux,nullptr))
    throw std::runtime_error("Can't link filesrc to matroskademux element");
  if(!gst_element_link_many(h264parse,nvh264dec,cudaconvert,cudadownload,nullptr))
    throw std::runtime_error("Can't link mamy element");

  g_signal_connect (matroskademux, "pad-added", G_CALLBACK (pad_append_handler), this);

  GstCaps* caps=gst_caps_new_simple("video/x-raw",
                                    "format",G_TYPE_STRING,"RGB",
                                    "width",G_TYPE_INT,1920,
                                    "height",G_TYPE_INT,1080,
                                    "framerate",GST_TYPE_FRACTION,30,1,
                                    nullptr);
  if(!gst_element_link_filtered(cudadownload,appsink,caps))
    throw std::runtime_error("Can't link appsrc to cudadownload use appsink caps");
  gst_caps_unref(caps);

  g_object_set (filesrc, "location", path.c_str(), nullptr);
  g_object_set (appsink, "max-buffers", 10, nullptr);

  gst_element_set_state(pipeline,GST_STATE_PLAYING);
}

MKV_Decode::~MKV_Decode()
{
  gst_element_set_state(pipeline,GST_STATE_NULL);
  gst_object_unref(GST_OBJECT(pipeline));
}

void MKV_Decode::GetNewImage(char* data)
{
  GstSample* sample = gst_app_sink_pull_sample((GstAppSink*)appsink);
  //从sample中获得缓存
  GstBuffer* buffer=gst_sample_get_buffer(sample);
  if(buffer!=nullptr)
  {
    GstMemory* mem=gst_buffer_peek_memory(buffer,0);
    gsize size=gst_memory_get_sizes(mem,nullptr,nullptr);
    memcpy(data,mem,size);
    gst_sample_unref(sample);
  }
  else
    throw std::runtime_error("Can't Get NewImage");
}
